package com.improvess.rna.som;

public class KNNBasedProbabilistcSOM extends ProbabilisticSOM {

	private int k;
	private double[][] treinamento;

	public KNNBasedProbabilistcSOM(int quantidadeDeNeuronios,
			int quantidadeDeAtributos, double sigmaVizinhancaInicial,
			int quantidadeDeRegistros, int k, double taxaDecrementoSigma) {
		super(quantidadeDeNeuronios, quantidadeDeAtributos,
				sigmaVizinhancaInicial, quantidadeDeRegistros, taxaDecrementoSigma);
		this.k = k;
		inicializarNeuronios();
	}

	@Override
	public int aprendizagem(double[][] treinamento) {
		this.treinamento = treinamento;
		int tempo = 0;
		double sigma = this.getSigmaVizinhanca();
		while (sigma > 0) {
			for (int i = 0; i < treinamento.length; i++) {

				NeuronioPb vencedor = (NeuronioPb) vencedor(i);

				int[] indicesDosVizinhos = encontrarKVizinhos(i, treinamento);

				for (Neuronio[] neuronios : this.getNeuronios()) {
					for (Neuronio neuronio : neuronios) {
						NeuronioPb neuronioPb = (NeuronioPb)neuronio;
						atualizarProbabilidades(i, neuronioPb, indicesDosVizinhos,
								vencedor, sigma);
					}
				}

			}
			sigma = sigma - this.getTaxaDecrementoSigma();
			tempo++;
		}

		return tempo;
	}

	private int[] encontrarKVizinhos(int indiceElemento, double[][] treinamento) {

		int[] retorno = new int[this.k];
		int pointer = 0;
		while (pointer < this.k) {
			for (int i = 0; pointer < retorno.length && i < treinamento.length; i++) {
				if (i != indiceElemento) {
					boolean teste = true;
					for (int j = 0; teste && j < pointer; j++) {
						teste = i != retorno[j];
					}

					if (teste) {
						double distanciadeI = distancia(indiceElemento, i);
						boolean encontrou = false;
						for (int w = 0; !encontrou && w < treinamento.length; w++) {
							if (w != indiceElemento) {
								for (int j = 0; teste && j < pointer; j++) {
									teste = w != retorno[j];
								}
								if (teste) {
									double distanciaDeW = distancia(
											indiceElemento, w);
									if (distanciaDeW < distanciadeI) {
										encontrou = true;
									}
								}
							}
						}
						if (!encontrou) {
							retorno[pointer] = i;
							pointer++;
						}
					}
				}
			}
		}
		return retorno;
	}

	private double distancia(int a, int b) {
		double[] xA = this.treinamento[a];
		double[] xB = this.treinamento[b];

		double produto = 0;
		for (int i = 0; i < xA.length; i++) {
			produto = produto + Math.pow(xA[i] - xB[i], 2);
		}

		return Math.sqrt(produto);
	}

	private void atualizarProbabilidades(int elemento, NeuronioPb neuronio,
			int[] indicesDosVizinhos, NeuronioPb vencedor, double sigma) {
		double [] probabilidades = neuronio.getProbabilidadesPosteriori();
		double delta = neuronio.funcaoVizinhanca(vencedor.getCoordenada(), sigma)/k;
		for(int i = 0; i < indicesDosVizinhos.length ; i++){
			
			probabilidades[indicesDosVizinhos[i]] = probabilidades[indicesDosVizinhos[i]] + delta;
			
		}
		
		probabilidades[elemento] = probabilidades[elemento] + delta;

	}

	public static void main(String[] args) {

		double[][] treinamento = { { 0, 1 }, { 3, 3 }, { 4, 4 }, { 1, 0 },
				{ 1, 1 } };

		for (int indice = 0; indice < treinamento.length; indice++) {

			for (int k = 1; k < treinamento.length - 1; k++) {

				KNNBasedProbabilistcSOM basedProbabilistcSOM = new KNNBasedProbabilistcSOM(
						1, 2, 0.5, 5, k, 0.015);
				basedProbabilistcSOM.treinamento = treinamento;

				int[] indices = basedProbabilistcSOM.encontrarKVizinhos(indice,
						treinamento);
				System.out.println("k = " + k + " e elemento = " +  "(" + treinamento[indice][0] + ", "  + treinamento[indice][1] + ")");
				for (int i : indices) {
					System.out.println("(" + treinamento[i][0] + ", "  + treinamento[i][1] + ")");
				}
				System.out.println();
			}
		}

	}
	
	public int getK() {
		return k;
	}
}
